// @ts-check
import * as THREE from 'three'
import _ from 'lodash'

class Bullet {
  constructor({
    x,
    y,
    z,
    viewX,
    viewY
  }) {
    this.speed = 5
    const geometry = new THREE.CubeGeometry(1, 1, 1)
    const material = new THREE.MeshBasicMaterial({ color: 'red' })
    const cube = new THREE.Mesh(geometry, material)
    cube.position.x = x
    cube.position.y = y
    cube.position.z = z
    this.cube = cube
    this.viewX = viewX
    this.viewY = viewY
  }

  next = () => {
    this.cube.position.z -= this.speed * Math.cos(this.viewY)
    this.cube.position.x -= this.speed * Math.sin(this.viewY)
    this.cube.position.y += this.speed * Math.sin(this.viewX)
  }
}

class Engine {
  constructor({
    canvas,
    width,
    height,
    onNext
  }) {
    this._moveSpeed = 1
    this._moveSteps = 0
    this._moveDirection = 'forward'
    this._isMoving = false
    this._vy = 0
    this._bullets = []

    this.onNext = onNext
    this.renderer = new THREE.WebGLRenderer({
      canvas
    })

    const scene = new THREE.Scene()
    const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
    const camera2 = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
    camera.rotation.set(0, 0, 0)
    camera2.position.x = 0
    camera2.position.y = 150
    camera2.position.z = 0
    camera2.rotateX(-Math.PI / 2)

    const axesHelper = new THREE.AxesHelper(150)
    scene.add(axesHelper)
    // scene.fog = new THREE.Fog(0x000000, 0, 750)
    // const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 )
    // scene.add(light)

    const geometry = new THREE.CubeGeometry(1, 1, 1)
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
    const yawObject = new THREE.Mesh(geometry, material)

    // const yawObject = new THREE.Object3D()
    const pitchObject = new THREE.Object3D()
    
    pitchObject.add(camera)
    yawObject.position.y = 10
    yawObject.add(pitchObject)
    scene.add(yawObject)
    scene.add(camera2)

    this.yawObject = yawObject
    this.pitchObject = pitchObject
    this.scene = scene
    this.camera = camera
    this.camera2 = camera2

    this.updateSize({ width, height })
    this.addLineFloor()
    this.addFloor()

    this._currCamera = this.camera
  }

  rotate = (event) => {
    const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0
    const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0

    this.yawObject.rotation.y -= movementX * 0.002
    this.pitchObject.rotation.x -= movementY * 0.002
  }

  get viewY() {
    return this.yawObject.rotation.y
  }

  get viewX() {
    return this.pitchObject.rotation.x
  }

  addLine = (start, end) => {
    const geometry = new THREE.Geometry()
    const material = new THREE.LineBasicMaterial({ color: 'black' })
    geometry.vertices.push(new THREE.Vector3(start.x, start.y, start.z))
    geometry.vertices.push(new THREE.Vector3(end.x, end.y, end.z))
    const line = new THREE.Line(geometry, material)
    this.scene.add(line)
  }

  addLineFloor = () => {
    const step = 10
    const size = 1000
    const offset = 500

    _.times(size / step, i => {
      const z = i * step - offset
      this.addLine({ x: -500, y: 0, z }, { x: 500, y: 0, z })
    })

    _.times(size / step, i => {
      const x = i * step - offset
      this.addLine({ x, y: 0, z: -500 }, { x, y: 0, z: 500 })
    })
  }

  addMassObjects = () => {
    const step = 10
    const size = 1000
    const offset = 500

    _.times(size / step, i => {
      _.times(size / step, j => {
        const x = i * step - offset
        const y = j * step - offset
        const geometry = new THREE.SphereGeometry(1, 1)
        const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
        const cube = new THREE.Mesh(geometry, material)
        cube.position.x = x
        cube.position.z = y
        // cube.position.y = Math.sin(x / 20) * 10
        this.scene.add(cube)
      })
    })
  }

  addFloor = () => {
    const geometry = new THREE.PlaneGeometry(1000, 1000, 1, 1)
    const material = new THREE.MeshBasicMaterial({ color: 'white', side: THREE.DoubleSide })
    const plane = new THREE.Mesh(geometry, material)
    plane.position.y = -1
    plane.rotateX(Math.PI / 2)
    this.scene.add(plane)
  }

  updateSize = ({ width, height }) => {
    this.renderer.setSize(width, height)
  }

  _move = () => {
    switch (this._moveDirection) {
      case 'forward':
        this.yawObject.position.z -= this._moveSpeed * Math.cos(this.viewY)
        this.yawObject.position.x -= this._moveSpeed * Math.sin(this.viewY)
        break
      case 'backward':
        this.yawObject.position.z += this._moveSpeed * Math.cos(this.viewY)
        this.yawObject.position.x += this._moveSpeed * Math.sin(this.viewY)
        break
      case 'left':
        this.yawObject.position.x -= this._moveSpeed * Math.cos(this.viewY)
        this.yawObject.position.z += this._moveSpeed * Math.sin(this.viewY)
        break
      case 'right':
        this.yawObject.position.x += this._moveSpeed * Math.cos(this.viewY)
        this.yawObject.position.z -= this._moveSpeed * Math.sin(this.viewY)
        break
      default:
        break
    }
    this._moveSteps--
  }

  shoot = () => {
    const bullet = new Bullet({
      x: this.yawObject.position.x,
      y: this.yawObject.position.y,
      z: this.yawObject.position.z,
      viewX: this.viewX,
      viewY: this.viewY
    })
    this._bullets.push(bullet)
    this.scene.add(bullet.cube)
  }

  switchCamera = () => {
    this._currCamera = this._currCamera === this.camera ? this.camera2 : this.camera
  }

  movePlayer = (direction, isMoving) => {
    this._isMoving = isMoving
    if (!isMoving) return
    this._moveDirection = direction
    this._moveSteps = 15
    this._move()
  }

  playerJump = () => {
    this._vy = 6
  }

  next = () => {
    this._bullets.forEach(b => {
      b.next()
    })

    if (this._moveSteps > 0 || this._isMoving) {
      this._move()
    }

    this.yawObject.position.y += this._vy
    const isOffGround = this.yawObject.position.y > 10

    if (isOffGround) {
      this._vy -= 0.5
    } else {
      this._vy = 0
      this.yawObject.position.y = 10
    }

    if (this.onNext) this.onNext(this)
  }

  start = () => {
    const animate = () => {
      requestAnimationFrame(animate)
      this.next()
      this.renderer.render(this.scene, this._currCamera)
    }

    animate()
  }
}

export default Engine
